// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Changeset = Microsoft.TeamFoundation.VersionControl.Client.Changeset;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.VersionControl.Common;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation;
using System.Globalization;
using System.Diagnostics;

namespace Microsoft.TeamFoundation.Migration.Toolkit.VC
{
    /// <summary>
    /// The change grouping method determines whether a TFS change group should be converted as a large change group
    /// or broken up into one change grouping per change item.  This is useful when migrating to systems that do not
    /// support grouped changesets.
    /// </summary>
    public enum ChangeGroupingMethod
    {
        OneChangePerGroup,
        ManyChangesPerGroup
    }

    /// <summary>
    /// The TFS->Source mirroring engine.  The basic operation is that a hashtable of algorithms (delegates)
    /// is created and for each change in a Changeset the proper algorithm is selected and run.
    /// </summary>
    public abstract class TfsToSourceAnalysisEngine
    {
        TfsAnalysisAlgorithms m_algorithms;

        // The collection of algorithms indexed by filtered (stripChangeType) TFS changetype
        Dictionary<ChangeType, TfsAnalysisAlgorithm> m_changeHandlers;

        protected TfsToSourceAnalysisEngine(VersionControlSession session, ChangeGroupingMananger manager)
        {
            Debug.Assert(session != null);
            Debug.Assert(manager != null);

            if (session == null)
            {
                throw new ArgumentNullException("session");
            }

            if (manager == null)
            {
                throw new ArgumentNullException("manager");
            }

            m_session = session;
            m_manager = manager;
        }

        /// <summary>
        /// Loads the analysis algorithms for this analysis engine.
        /// </summary>
        /// <returns>The loaded algorithms.</returns>
        protected abstract TfsAnalysisAlgorithms LoadAnalysisAlgorithms();

        /// <summary>
        /// Create a new change grouping for the provided changeset.
        /// </summary>
        /// <param name="changeSet">The changeset the change grouping will represent</param>
        /// <returns>The created change grouping</returns>
        protected abstract ChangeGrouping CreateChangeGrouping(Changeset changeSet);

        /// <summary>
        /// Gets the grouping method being used during this migration.  (Default: ManyChangesPerGroup)
        /// </summary>
        protected virtual ChangeGroupingMethod GroupingMethod
        {
            get
            {
                return ChangeGroupingMethod.ManyChangesPerGroup;
            }
        }

        /// <summary>
        /// Called when a exception occurs during the analysis process.  This allows the derived class to allow the process
        /// to continue or abort based on the condition.
        /// </summary>
        /// <param name="me">The exception that occurred.</param>
        /// <returns>If true the analysis is allowed to continue.  If false the analysis phase should abort.</returns>
        protected virtual bool HandleMigrationException(Exception me)
        {
            TraceManager.TraceError(me, false);
            return false;
        }

        /// <summary>
        /// Finds the appropriate version control mapping for the provided TFS change.  If there is no appropriate mapping
        /// null should be returned.
        /// </summary>
        /// <param name="change">The change to lookup</param>
        /// <returns>The found mapping or null if no appropriate mapping is found.</returns>
        protected abstract VersionControlMapping FindChangeMapping(Change change);

        private void lazyInit()
        {
            if (m_algorithms == null)
            {
                m_algorithms = LoadAnalysisAlgorithms();
                buildChangeHandler();
            }
        }

        /// <summary>
        /// Analyzes the TFS changeset to generate change groupings to migrate to the source system.
        /// </summary>
        /// <param name="changeSet">The changeset to analyze.</param>
        /// <returns>The number of changes added to the change grouping.</returns>
        public virtual int Process(Changeset changeSet)
        {
            if (changeSet == null)
            {
                throw new ArgumentNullException("changeSet");
            }

            lazyInit();

            TraceManager.TraceInformation("Starting analysis of TFS change {0}", changeSet.ChangesetId);
            Session.OnAnalyzingChangeStarting(new VersionControlEventArgs(changeSet.ChangesetId.ToString(CultureInfo.InvariantCulture),SystemType.Tfs));

            int changeCount = 0;
            ChangeGrouping group = null;

            if (GroupingMethod == ChangeGroupingMethod.ManyChangesPerGroup)
            {
                group = CreateChangeGrouping(changeSet);

                if (changeSet != null)
                {
                    foreach (Change c in changeSet.Changes)
                    {
                        Session.ThrowIfAborted();
                        
                        if (FindChangeMapping(c) != null)
                        {
                            TfsAnalysisAlgorithm alg = GetHandler(c.ChangeType);

                            try
                            {
                                Execute(alg, c, group);
                            }
                            catch (MissingMethodException mme)
                            {
                                throw new VersionControlMigrationException(
                                    string.Format(MigrationToolkitVCResources.Culture, 
                                    MigrationToolkitVCResources.ClientNotSupported), mme);
                            }
                        }
                    }
                }

                changeCount = group.Actions.Count;

                if (group.Actions.Count > 0)
                {
                    group.Save();
                }
            }
            else
            {
                if (changeSet != null)
                {
                    Change[] reorderedChanges = ReorderChangeset(changeSet);
                    foreach (Change c in reorderedChanges)
                    {
                        Session.ThrowIfAborted();

                        if (FindChangeMapping(c) != null)
                        {
                            group = CreateChangeGrouping(changeSet);
                            TfsAnalysisAlgorithm alg = GetHandler(c.ChangeType);

                            Execute(alg, c, group);

                            if (group.Actions.Count > 0)
                            {
                                group.Save();
                                changeCount++;
                            }
                        }
                    }
                }
            }
            if (changeCount == 0)
            {
                TraceManager.TraceInformation("No relevent changes found in TFS change {0}",
                    changeSet.ChangesetId);
            }
            Session.OnAnalyzingChangeComplete(new VersionControlEventArgs(group, changeSet.ChangesetId.ToString(CultureInfo.InvariantCulture), SystemType.Tfs));
            return changeCount;
        }

        protected virtual Change[] ReorderChangeset(Changeset changeset)
        {
            return changeset.Changes;
        }

        /// <summary>
        /// Executes the analysis algorithm.
        /// </summary>
        /// <param name="algorithm">The algorithm to execute.</param>
        /// <param name="change">The change parameter for the algorithm.</param>
        /// <param name="group">The group paramter for the algorithm.</param>
        protected void Execute(TfsAnalysisAlgorithm algorithm, Change change, ChangeGrouping group)
        {
            if (algorithm == null)
            {
                throw new ArgumentNullException("algorithm");
            }

            try
            {
                algorithm(change, group);
            }
            catch (MigrationException me)
            {
                if (!HandleMigrationException(me))
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Removed the lock, none and encoding bits from the changetypes to normalize them for hashtable lookup
        /// </summary>
        /// <param name="changeType">The changetype to normalize</param>
        /// <returns>The normalized changetype</returns>
        internal static ChangeType stripChangeType(ChangeType changeType)
        {
            changeType &= ~ChangeType.Lock;
            changeType &= ~ChangeType.Encoding;
            if (changeType != ChangeType.None)
            {
                changeType &= ~ChangeType.None;
            }

            return changeType;
        }

        /// <summary>
        /// Returns the designated handler for the changetype (default is TFSAlgorithms.Unhandled)
        /// </summary>
        /// <param name="changeType">The changetype to handle.</param>
        /// <returns>The delegate that handles the changetype</returns>
        protected virtual TfsAnalysisAlgorithm GetHandler(ChangeType changeType)
        {
            lazyInit();

            TfsAnalysisAlgorithm alg;

            if (!m_changeHandlers.TryGetValue(stripChangeType(changeType), out alg))
            {
                alg = m_algorithms.Unhandled;
            }

            return alg;
        }

        /// <summary>
        /// The current session.
        /// </summary>
        public virtual VersionControlSession Session
        {
            get
            {
                return m_session;
            }

            protected set
            {
                m_session = value;
            }
        }

        /// <summary>
        /// The current change grouping mananger
        /// </summary>
        public virtual ChangeGroupingMananger Manager
        {
            get
            {
                return m_manager;
            }

            protected set
            {
                m_manager = value;
            }
        }


        /// <summary>
        /// Adds a handler delegate to the change handler hashtable.  Use this to add handles instead of direct access
        /// to avoid unintentionally adding a duplicate handler.
        /// </summary>
        /// <param name="changeType">The changetype to handle (the collection key)</param>
        /// <param name="algorithm">The delegate handler (the collection value)</param>
        void addHandler(ChangeType changeType, TfsAnalysisAlgorithm algorithm)
        {
            Debug.Assert(m_changeHandlers != null);

            if (m_changeHandlers.ContainsKey(changeType))
            {
                throw new MigrationException(string.Format(CultureInfo.InvariantCulture, "There already exists a handler for: {0}", changeType));
            }

            m_changeHandlers[changeType] = algorithm;
        }

        /// <summary>
        /// Builds the algorithms hashtable when QueryMerges is not available
        /// </summary>
        void buildChangeHandler()
        {
            m_changeHandlers = new Dictionary<ChangeType, TfsAnalysisAlgorithm>();

            addHandler(Add, m_algorithms.Add);
            addHandler(Add | Edit, m_algorithms.Add);

            addHandler(Branch, m_algorithms.Branch);
            addHandler(BranchMerge, m_algorithms.BranchMerge);

            addHandler(BranchMerge | Delete, m_algorithms.BranchMergeDelete);
            addHandler(BranchMerge | Edit, m_algorithms.BranchMergeEdit);

            addHandler(Rename, m_algorithms.Rename);
            addHandler(Rename | Edit, m_algorithms.RenameEdit);
            addHandler(Rename | Undelete, m_algorithms.RenameUndelete);
            addHandler(Rename | Merge, m_algorithms.RenameMerge);
            addHandler(Rename | Merge | Delete, m_algorithms.RenameDeleteMerge);
            addHandler(Rename | Edit | Merge, m_algorithms.RenameEditMerge);
            addHandler(Rename | Undelete | Merge, m_algorithms.RenameUndeleteMerge);
            addHandler(Rename | Edit | Undelete, m_algorithms.RenameEditUndelete);
            addHandler(Rename | Edit | Undelete | Merge, m_algorithms.RenameEditUndeleteMerge);
            addHandler(Rename | Delete | Undelete, m_algorithms.RenameDeleteUndelete);
            addHandler(Rename | Delete, m_algorithms.RenameDelete);

            addHandler(Merge, m_algorithms.Merge);
            addHandler(Merge | Delete, m_algorithms.MergeDelete);
            addHandler(Merge | Delete | Undelete, m_algorithms.MergeDeleteUndelete);
            addHandler(Merge | Undelete, m_algorithms.MergeUndelete);
            addHandler(Merge | Edit, m_algorithms.MergeEdit);
            addHandler(Merge | Edit | Undelete, m_algorithms.MergeEditUndelete);

            addHandler(Branch | Edit, m_algorithms.BranchEdit);
            addHandler(Branch | Delete, m_algorithms.BranchDelete);

            addHandler(Undelete, m_algorithms.Undelete);
            addHandler(Delete, m_algorithms.Delete);
            addHandler(Edit, m_algorithms.Edit);
            addHandler(Edit | Undelete, m_algorithms.EditUndelete);
            addHandler(Delete | Undelete, m_algorithms.DeleteUndelete);

            addHandler(None, m_algorithms.None);
        }

        const ChangeType Add = ChangeType.Add;
        const ChangeType Rename = ChangeType.Rename;
        const ChangeType Edit = ChangeType.Edit;
        const ChangeType Delete = ChangeType.Delete;
        const ChangeType Undelete = ChangeType.Undelete;
        const ChangeType Branch = ChangeType.Branch;
        const ChangeType Merge = ChangeType.Merge;
        const ChangeType BranchMerge = Merge | Branch;
        const ChangeType None = ChangeType.None;

        VersionControlSession m_session;
        ChangeGroupingMananger m_manager;
    }

    public class NewChangeGroupEventArgs : EventArgs
    {
        ChangeGrouping m_grouping;

        public NewChangeGroupEventArgs(ChangeGrouping group)
        {
            m_grouping = group;
        }

        public ChangeGrouping Grouping
        {
            get { return m_grouping; }
        }

    }
}
